1. FreeRTOSConfig.h: RTOS相关的宏编译开关，用来剪裁RTOS的功能

    configUSE_PREEMPTION
        =1,表示使用抢占式调度算法功能
    configUSE_16_BIT_TICKS 
        =0: tick计数采用32位数，
        =1：tick计数采用6位数，
    configTOTAL_HEAP_SIZE： 
        堆内存的大小，heap1.c,heap2.c,heap4.c
    configUSE_MALLOC_FAILED_HOOK=1,
        需要开发者实现内存分配失败时调用的钩子函数vApplicationMallocFailedHook()
    configMINIMAL_STACK_SIZE:
        IDLE任务使用的栈空间大小，创建任务默认的最小值
    configMAX_PRIORITIES：优先级的最大数量
        0是最低的优先级，值应在满足要求的情况下尽量小
        实际任务的优先级范围=0 ~ (configMAX_PRIORITIES-1)
    configUSE_PORT_OPTIMISED_TASK_SELECTION:
        =0 或未定义： 常规方式，configMAX_PRIORITIES无限制
        =1 ：configMAX_PRIORITIES不超过32
    configTICK_RATE_HZ：
        tick中断的频率,时间分片值
        调度器在每个时间分片的结尾，选择下一个要执行的任务
        典型值=100，即时间分片=10ms
    configIDLE_SHOULD_YIELD：
        阻止空闲任务消耗CPU时间
    INCLUDE_vTaskPrioritySet：
        =1时，才能设置任务优先级
    INCLUDE_uxTaskPriorityGet:
        =1时，才能获取任务的优先级
    INCLUDE_vTaskDelete：
        =1时，才能删除任务
    configUSE_PREEMPTION：
        =1时，使用抢占式调度算法
    configUSE_TIME_SLICING：
        =1时，使用时间分片算法
    configUSE_TICKLESS_IDLE：
        =1： 
            the tick interrupt being turned off completely for extended
             periods
    configIDLE_SHOULD_YIELD：
        =0，
            IDLE任务在自己的时间片内，不会被高优先级的任务抢占
            If configIDLE_SHOULD_YIELD is set to 0 then the Idle task will remain in the Running
            state for the entirety of its time slice, unless it is preempted by a higher priority task.
        =1：
            执行结束自愿退出
            yield (voluntarily give
            up whatever remains of its allocated time slice) on each iteration of its loop if there are
            other Idle priority tasks in the Ready state
    configUSE_QUEUE_SETS：
        =1：
            Queue Set功能开启
    configUSE_TIMERS：
        开启软件定时器功能
    configTIMER_TASK_PRIORITY:
        DemoTask的优先级
    configTIMER_TASK_STACK_DEPTH:
        DemoTask的栈大小
    INCLUDE_vTaskSuspend：
        =1：?????
    configUSE_TASK_NOTIFICATIONS：
        =1：开启任务通知功能
** FreeRTOS可以完全使用静态分配的方法进行
** heap1.c~heap5.c提供了5中堆内存的分配方法，根据实际需要选择其中一种方式使用

使用freeRTOS创建一个新的工程的步骤：
    (1) 相关代码文件
    (2) 设置相关必要的中断

1.5 数据类型和代码风格
    TickType_t :u32 或者u16, 主要用来存储自CPU启动开始的Tick中断数
    BaseType_t :Typically,32-bit type on a 32-bit architecture, a 16-bit type on a
                16-bit architecture, and an 8-bit type on an 8-bit architecture.
    变量名称前缀：
        c:  char 类型
        s:  int16_t
        i:  int32_t
        x:  BaseType_t 以及其他非标准的类型
        u:  unsigned
        p:  pointer 类型
    函数名称前缀：返回值的类型
        v: void 类型返回值
        x: BaseType_t 类型的返回值
        pv: void pointer 类型
        prv: 文件内的私有函数名前缀
    宏名称：
        一般全用大写表示
        开头使用小写，表明所在的文件
            例如configUSE_PREEMPTION，表示在FreeRTOSConfig.h文件中定义

2 堆内存管理
  注：若完全采用静态分配的方式创建对象，则可以不用包含堆内存相关的文件 heap1.c~heap5.c
freertos内核中的对象可以采用静态分配的方式，也可以采用动态内存的方式(使用堆内存)
编译器提供的堆内存管理策略，不一定满足实时操作系统的要求，因此采用freertos提供了一组专用于实时操作系统的堆内存管理策略，即heap1.c~heap5.c
    申请堆内存：
        pvPortMalloc()
    释放内存：
        vPortFree()
2.1 Heap1.c的堆内存策略

    使用场景：
        不允许释放堆内存
        在调度器启动之前动态分配内存，应用运行期间不释放内存,也不能释删除任务，内核相关的对象
    优点：
        确定性，不会导致内存碎片化
    
    堆空间在编译时静态声明，仅仅简单实现了pvPortMalloc()函数

2.2 Heap2.c的堆内存策略

    向后兼容，deprecated,建议使用heap4.c
    采用最优匹配算法分配内存，允许释放内存
    堆空间在编译时静态声明

    缺点：
        容易导致内存碎片化
        不确定性
    优点：
        速度快
    使用场合：
        重复创建或释放同样大小的对象
    
2.3 Heap3.c的堆内存策略
    使用标准库中的malloc()函数和free()函数实现
    不受宏定义configTOTAL_HEAP_SIZE的影响

    实现线程安全的方式：
        挂起RTOS调度器

2.4 Heap4.c的堆内存策略

    堆空间在编译时静态声明
    采用first fit algorithm来分配内存
        即优先选择第一个满足的最大的free内存块，从中分配一块内存，剩余依然可供再次分配使用

    自动合并临近free内存空间，可有效减少内存碎片化


    尽量堆内存分配在速度比较快的内部内存芯片上，加快速度

    默认情况下，堆内存块在heap4.c文件中定义
        configAPPLICATION_ALLOCATED_HEAP=1时，则需要开发者自己定义堆内存区域
            uint8_t ucHeap[xxx];

2.5 Heap5.c的堆内存策略
    申请内存和释放内存采用的算法与heap4.c完全相同
    不局限于从一个单独的内存块分配内存，可以在多个不同的内存块申请和释放内存
    适用于存在多片不连续内存的系统
        在首次调用函数 pvPortMalloc(), Heap5.c使用的内存必须先进行初始化
            即使用函数vPortDefineHeapRegions() 初始化堆内存区间和范围
    示例：
    typedef unsigned char uint8_t 
    #define RAM1_START_ADDRESS ( ( uint8_t * ) 0x00010000 )
    #define RAM1_SIZE ( 65 * 1024 )
    #define RAM2_START_ADDRESS ( ( uint8_t * ) 0x00020000 )
    #define RAM2_SIZE ( 32 * 1024 )
    #define RAM3_START_ADDRESS ( ( uint8_t * ) 0x00030000 )
    #define RAM3_SIZE ( 32 * 1024 )
    /* Create an array of HeapRegion_t definitions, with an index for each of the three
    RAM regions, and terminating the array with a NULL address. The HeapRegion_t
    structures must appear in start address order, with the structure that contains the
    lowest start address appearing first. */
    const HeapRegion_t xHeapRegions[] =
    {
    { RAM1_START_ADDRESS, RAM1_SIZE },
    { RAM2_START_ADDRESS, RAM2_SIZE },
    { RAM3_START_ADDRESS, RAM3_SIZE },
    { NULL, 0 } /* Marks the end of the array. */
    };
    int main( void )
    {
    /* Initialize heap_5. */
    vPortDefineHeapRegions( xHeapRegions );
    /* Add application code here. */
    }

2.6 堆内存相关的函数
    // 获取堆中未分配的空间大小
    size_t xPortGetFreeHeapSize( void );
    // 曾经最小的未分配堆空间的大小
    // 可以调用此函数才查看堆内存的是否够用
    xPortGetMinimumEverFreeHeapSize();

    // 堆内存分配失败时调用的钩子函数
    // configUSE_MALLOC_FAILED_HOOK=1,需要开发者手动以内存分配时的钩子函数
    void vApplicationMallocFailedHook( void );


3 任务管理
    (1) 必须至少有一个任务处于执行状态
    (2) Idle 任务总是会被创建
    (3) 不能阻塞Idle任务，即Idle任务总是处于就绪状态

    // 创建任务
    BaseType_t xTaskCreate( TaskFunction_t pvTaskCode, //任务主函数
                const char * const pcName,//任务名称
                uint16_t usStackDepth,//任务栈的深度，实际栈size=usStackDepth*sizeof(uint32_t)
                void *pvParameters,//函数pvTaskCode的参数
                UBaseType_t uxPriority,//任务优先级
                TaskHandle_t *pxCreatedTask );//创建成功返回的任务句柄
        返回值：
            pdPASS：创建成功
            pdFAIL：失败
    
    pdMS_TO_TICKS() 将ms转换为tick count

    3.1 任务状态
        阻塞状态
            指定的事件发生，或者超时退出阻塞状态
        运行状态
        挂起状态
            进入挂起状态的唯一方法是
                调用函数vTaskSuspend()
            退出挂起状态的方式：
                 vTaskResume()或者xTaskResumeFromISR() 
        就绪状态

        //延迟指定的时间，任务进入阻塞状态
        void vTaskDelay( TickType_t xTicksToDelay );
        // the exact tick count value at which the
        // calling task should be moved from the Blocked state into the Ready state.
        void vTaskDelayUntil( TickType_t * pxPreviousWakeTime, TickType_t xTimeIncrement );
    
    3.2 Idle任务以及Idle任务钩子
        
        Idle任务：
            (1) Idle任务不能尝试阻塞或者挂起
            (2) If the application makes use of the vTaskDelete() API function, then the Idle task hook
                must always return to its caller within a reasonable time period
        Idle任务钩子函数void vApplicationIdleHook( void )
            (1) 执行低优先级，后台连续性处理的任务
            (2) 测量处理器的空闲时间
            (3) 处理器进入节能模式
    3.3 改变任务优先级
        //改变任务的优先级
        //INCLUDE_vTaskPrioritySet需=1
        void vTaskPrioritySet( TaskHandle_t pxTask, UBaseType_t uxNewPriority );
        // 获取任务的优先级
        // INCLUDE_uxTaskPriorityGet = 1
        UBaseType_t uxTaskPriorityGet( TaskHandle_t pxTask );
    
    3.4 删除任务
        // 删除任务
        void vTaskDelete( TaskHandle_t pxTaskToDelete );
    
    3.5 调度算法
        
        抢占式，
        相同优先级采用时间分片算法

    3.6 协作调度
        configUSE_PREEMPTION=0
        configUSE_TIME_SLICING=任意值
        仅当正在执行的任务进入阻塞状态或者显式退出(taskYIELD)才会发生上下文切换
    
4 队列管理
  用来在任务与人物，任务与中断，中断与与任务之间进行通信
  4.1 队列的特点
    FIFO
    队列中的数据是要传输数据的拷贝，包括指针
  4.2 队列的使用
        // 静态方式创建队列，大小在编译时分配
        xQueueCreateStatic()
        // 动态方式分配
        QueueHandle_t xQueueCreate( UBaseType_t uxQueueLength, 
            UBaseType_t uxItemSize );
        // 将数据放至队列前面
        BaseType_t xQueueSendToFront( QueueHandle_t xQueue,
            const void * pvItemToQueue,
            TickType_t xTicksToWait );
        BaseType_t xQueueSendToBack( QueueHandle_t xQueue,
            const void * pvItemToQueue,
            TickType_t xTicksToWait );
        // 从队列中接收数据
        BaseType_t xQueueReceive( QueueHandle_t xQueue,
            void * const pvBuffer,
            TickType_t xTicksToWait );
        // 查询当前队列中的消息数量
        UBaseType_t uxQueueMessagesWaiting( QueueHandle_t xQueue );
    4.3 从多个地方接收数据
    4.4 Queuing Poingters
            (1) 在任务之间通过指针共享内存时，应确保不同任务不能同时修改共享的内存
             或者执行任务可能导致内存失效或者变化的操作
             理想情况是：
                  发送任务在指针入队之前能够操作指针，入队之后不能操作指针
                  接收任务在从队列中获取到指针之后才能操作对应的内存
             (2) 内存应一致保持有效
    4.5 通过指针传递不同类型的数据
            typedef enum{
                TCPEvent=0,
                UDPEvent,
                HTTPEvent,
            }NetEvent_t;

            typedef struct{
                NetEvent_t netEnent;
                void *data;
            };
        4.6 从多个队列接收数据
            使用Queue Sets
                当sets中某个队列接收到数据是=时，这个队列的句柄将会发送给set, 直到任务尝试从set中读取数据时，才会返回
            
            相关函数
                // 创建 queueSets
                // @param uxEventQueueLength
                // 仅当队列接受到数据时，该队列的句柄才会被发送给QueueSets
                //Therefore, the maximum number of items the queue set
                //will ever have to hold at one time is the sum of the lengths of every
                //queue in the set.
                // Semaphores 亦可以加入到QueueSet中
                // Binary Semaphore实际上是队列长度=1的队列
                // Counting Semaphore实际上是队列长度n的队列         
                QueueSetHandle_t xQueueCreateSet( const UBaseType_t uxEventQueueLength );
                // 将队列或者信号加入到Queue Set中
                BaseType_t xQueueAddToSet( QueueSetMemberHandle_t xQueueOrSemaphore,
                            QueueSetHandle_t xQueueSet );
                // 返回接收到数据的队列的句柄
                QueueSetMemberHandle_t xQueueSelectFromSet( QueueSetHandle_t xQueueSet,
                            const TickType_t xTicksToWait )
        4.7 使用队列创建邮箱
            长度为1的队列
            可以被任何任务读取，或者中断服务程序
            mailbox中的数据并不传递，而是一直保留在mailbox中
            发送者向mailbox中写入数值，接收者读取数据，但是并不会从mailbox移除数据
            // 写mailbox中写入数据
            //  队列的长度要求=1
            BaseType_t xQueueOverwrite( QueueHandle_t xQueue, const void * pvItemToQueue )
            // 从队列的开头读取数据，但并不移除数据
            BaseType_t xQueuePeek( QueueHandle_t xQueue,
                void * const pvBuffer,
                TickType_t xTicksToWait );


5.0 软件定时器管理
    用来在指定的时间调用指定的函数执行
    或者固定频率的执行某一操作
    
    无需硬件支持，与硬件定时器和硬件计数器

    configUSE_TIMERS=1时，软件定时器功能才有效
5.2 定时器回调函数
    回调函数执行时间应尽可能短，并且不能进入阻塞状态
    可以使用函数xQueueReceive() 但是参数xTickToWait必须等于0
    // 回调函数原型
    void ATimerCallback( TimerHandle_t xTimer )

5.3 定时器类型
   
    auto-reload

    o---->[xTimerCreate()]------->Dormant<-----------
                                    ↓               ↑
                                    ↓               ↑
    xTimerStart()------------------>↓               ↑
    XTimerReset()------------------>↓        xTimerStop()
    XTimerChangePeriod()----------->↓               ↑
                                    ↓               ↑
                                    ↓               ↑
                   |<------------Running----------->↑
        执行回调函数|                ↑
                   |--------------->↑
                    下一次定时时间到
    one-shot

    o---->[xTimerCreate()]------->Dormant<-------------------<---------------
                                    ↓               ↑                       ↑
                                    ↓               ↑                       ↑
    xTimerStart()------------------>↓               ↑                       ↑
    XTimerReset()------------------>↓        xTimerStop()      定时时间到,回调函数执行结束
    XTimerChangePeriod()----------->↓               ↑                       ↑
                                    ↓               ↑                       ↑
                                    ↓               ↑                       ↑
                                  Running---------->↑                       ↑
                                    ↓                                       ↑
                                    ↓                                       ↑            
                                    ↓---------------------------------------↑

5.4 定时器的执行上下文
    在demoTask中执行，
    demoTask在调度器启动的时候自动创建
    demoTask的优先级和栈的大小在编译时由宏定义
         configTIMER_TASK_PRIORITY:DemoTask的优先级
         configTIMER_TASK_STACK_DEPTH:DemoTask的栈大小
    定时器实现的机制：
        在普通任务向demoTask发送定时器消息队列(timer command queue),该队列的长度由
        configTIMER_QUEUE_LENGTH决定
    API函数
        // 创建定时器
        TimerHandle_t xTimerCreate( const char * const pcTimerName,
            TickType_t xTimerPeriodInTicks,//定时时间
            UBaseType_t uxAutoReload,//是否自动重装
            void * pvTimerID,//定时器ID
            TimerCallbackFunction_t pxCallbackFunction );//定时到执行的回调函数
        // 启动定时器
        TimerHandle_t xTimerStart( TimerHandle_t xTimer, TickType_t xTicksToWait )
        // 设置定时器的ID,不发命令给demotask,直接改
        void vTimerSetTimerID( const TimerHandle_t xTimer, void *pvNewID );
        // 获取定时器的id,不发命令给demotask,直接读
        void *pvTimerGetTimerID( TimerHandle_t xTimer );
        // 改变定时器的定时周期，发命令给demotask
        // 超时时间将以改变周期的时间点为起始重新开始计算
        // 处于dormant状态时，调用该函数，在设定超时间后，定时器将进入running状态
        BaseType_t xTimerChangePeriod( TimerHandle_t xTimer,
            TickType_t xNewTimerPeriodInTicks,
            TickType_t xTicksToWait );
        // 重置软件定时器，发送命令给demoTask
        BaseType_t xTimerReset( TimerHandle_t xTimer, TickType_t xTicksToWait );

6.0 中断管理
    (1) 任务不依赖于硬件，优先级通过软件设定，软件调度器决定那个任务进入运行状态
    (2) 中断是由硬件决定的，任务仅在没有中断的情况下运行，优先级最低的中断也会中断最高优先级的任务
        因此任务是不能抢占ISR的
    taskYIELD() //在任务中调用，请求进行上下文切换
    // 中断函数
    portYIELD_FROM_ISR()
    portEND_SWITCHING_ISR()
6.3 中断延迟处理
    在ISR中发出信号量，在任务中等待信号量
    // 创建一个二进制的信号量
    SemaphoreHandle_t xSemaphoreCreateBinary( void )
    // 读取信号量
    // 除了递归锁之外，别的信号量均可以使用此函数获取信号量
    aseType_t xSemaphoreTake( SemaphoreHandle_t xSemaphore, TickType_t xTicksToWait );
    // 在ISR中发出信号量
    BaseType_t xSemaphoreGiveFromISR( SemaphoreHandle_t xSemaphore,
        BaseType_t *pxHigherPriorityTaskWoken );
    // 在普通任务中发出信号量
     BaseType_t xSemaphoreGive SemaphoreHandle_t xSemaphore,
        BaseType_t *pxHigherPriorityTaskWoken );
    // 创建计数信号量
    SemaphoreHandle_t xSemaphoreCreateCounting( UBaseType_t uxMaxCount,
        UBaseType_t uxInitialCount );


6.6 延迟中断到DemoTask中
    优点：在DemoTask中集中处理中断，避免为每个中断创建单独的任务
    缺点：
        降低灵活性，不能为每个延迟的中断设置单独的优先级
        增加了不确定性
    API函数：
        // 延迟函数原型
            void vPendableFunction( void *pvParameter1, uint32_t ulParameter2 );
        //
        BaseType_t xTimerPendFunctionCallFromISR( PendedFunction_t xFunctionToPend,
            void *pvParameter1,//延迟函数的参数1，可以是任意数据类型
            uint32_t ulParameter2,//延迟函数的参数2
            BaseType_t *pxHigherPriorityTaskWoken );

        BaseType_t xTimerPendFunctionCall( PendedFunction_t xFunctionToPend,
            void *pvParameter1,
            uint32_t ulParameter2,
            BaseType_t *pxHigherPriorityTaskWoken );


6.6 在ISR中使用队列
    // 将数据放置队列的首部
    BaseType_t xQueueSendToFrontFromISR( QueueHandle_t xQueue,
        void *pvItemToQueue
        BaseType_t *pxHigherPriorityTaskWoken
        )
    // 将队列放到数据的尾部
    BaseType_t xQueueSendToBackFromISR( QueueHandle_t xQueue,
        void *pvItemToQueue
        BaseType_t *pxHigherPriorityTaskWoken
        );

6.8 中断嵌套
    任务的优先级与中断的优先级无任何关系
    硬件决定那个中断执行，软件决定哪个任务执行
    
    configMAX_SYSCALL_INTERRUPT_PRIORITY(旧版) -------|
                                                      |-------含义相同，表示使用FreeRTOS API函数的中断的最大的优先级      
    configMAX_API_CALL_INTERRUPT_PRIORITY(新版)--------|
    
    configKERNEL_INTERRUPT_PRIORITY：
        设置tick中断使用的优先级，应尽可能低
        若configMAX_SYSCALL_INTERRUPT_PRIORITY未定义，则任何使用interrupt-safe FreeRTOS API functions的中断的有限级必须等于
        configKERNEL_INTERRUPT_PRIORITY定义的值




7. 资源管理
    (1) Critcal Section
        时间必须非常短，否则将影响中断的调度
        taskENTER_CRITICAL() //禁止系统中断，禁止的系统中断的优先级的最大值= configMAX_SYSCALL_INTERRUPT_PRIORITY
        ...
        taskEXIT_CRITICAL()

        在ISR中对应的函数为
            taskENTER_CRITICAL_FROM_ISR()
            taskEXIT_CRITICAL_FROM_ISR()
    (2) 挂起调度器或者锁调度器
        void vTaskSuspendAll( void );
        BaseType_t xTaskResumeAll( void )
    (3) 互斥量
        binary Semaphore的一直特殊类型，不同的是
            SemaphoreHandle_t xSemaphoreCreateMutex( void );
                
                xSemaphoreTake( xMutex, portMAX_DELAY );
                ...
                xSemaphoreGive( xMutex );
        优先级翻转：
        优先级继承:
        互斥量死锁：
        递归锁：
    (4) Gatekeeper Tasks
        对指定资源具有独占权，只有gatekeep task才能直接获取该资源，其他任务只能通过使用gatekeeper提供的服务任务间接访问

        configUSE_TICK_HOOK：
            =1，tick中断时调用的钩子函数void vApplicationTickHook( void );

8.0 Event Groups
    可以一次等待多个事件的发生
    当事件发生时，可以使所有等待该事件的任务解除阻塞状态
    
    可以采用该机制在多个任务之间进行同步
    
    相比信号量消耗内存少
8.2 Event Group的特点
    高8位未使用
    configUSE_16_BIT_TICKS=1
        EventGroup只有低8位可以用来传递事件
    configUSE_16_BIT_TICKS=0
        EventGroup只有低24位可以用来传递事件
    
    高8位不使用
    //创建事件
    EventGroupHandle_t xEventGroupCreate( void );
    // 设置事件标志
    EventBits_t xEventGroupSetBits( EventGroupHandle_t xEventGroup,
        const EventBits_t uxBitsToSet );
    // 在中断函数中使用
    BaseType_t xEventGroupSetBitsFromISR( EventGroupHandle_t xEventGroup,
        const EventBits_t uxBitsToSet,
        BaseType_t *pxHigherPriorityTaskWoken )
    //等待事件发生
    EventBits_t xEventGroupWaitBits( const EventGroupHandle_t xEventGroup,
        const EventBits_t uxBitsToWaitFor,//要等待的时间
        const BaseType_t xClearOnExit,//任务解除堵塞状态是是否清楚等待的事件位
        const BaseType_t xWaitForAllBits,//是否等待所有事件
        TickType_t xTicksToWait );
    //使用Event Group进行任务同步
    EventBits_t xEventGroupSync( EventGroupHandle_t xEventGroup,
        const EventBits_t uxBitsToSet,
        const EventBits_t uxBitsToWaitFor,
        TickType_t xTicksToWait )

9 任务通知
  直接与任务进行通信

  一对一

  每个任务
  通知状态(u32)：
    Pending:接收到通知后，Pending状态 
    Not Pending:任务读取通知后，状态变为Not Pending
  通知值(u32):
    
        
  优点：速度快，消耗内存少
  缺点：
        不能向ISP发送通知

  // 向任务发送通知
  BaseType_t xTaskNotifyGive( TaskHandle_t xTaskToNotify );
  // 
  void vTaskNotifyGiveFromISR( TaskHandle_t xTaskToNotify,
        BaseType_t *pxHigherPriorityTaskWoken );
  // 读取通知值
   uint32_t ulTaskNotifyTake( BaseType_t xClearCountOnExit, //=TRUE.读取完成后清零，FALSE,读取完成后数量=0
        TickType_t xTicksToWait );
    
   BaseType_t xTaskNotify( TaskHandle_t xTaskToNotify,
        uint32_t ulValue,//通知值
        eNotifyAction eAction );//枚举类型，eNoAction、eSetBits、eIncrement、eSetValueWithoutOverwrite、eSetValueWithOverwrite
    BaseType_t xTaskNotifyFromISR( TaskHandle_t xTaskToNotify,
        uint32_t ulValue,
        eNotifyAction eAction,
    BaseType_t *pxHigherPriorityTaskWoken );
    // 等待通知
    BaseType_t xTaskNotifyWait( uint32_t ulBitsToClearOnEntry,
        uint32_t ulBitsToClearOnExit,
        uint32_t *pulNotificationValue,
        TickType_t xTicksToWait )

11. 开发者支持
    configASSERT()//Assert a condoition,same with assert() in C

    FreeRTOS+TRACE

    Malloc Failed HOOK
    
    Stack overflow hook

    Task Run-Time Statistics
        uxTaskGetSystemState()
        vTaskGetRunTimeStats() 
    The Run-Time Statistics Clock
        run-time frequency should =(10~100)* tick interrupt 频率的10~100倍
        频率越高，统计数据也越精确，但是也使得定时器的溢出更快到来
    
    configGENERATE_RUN_TIME_STATS：
    
    portCONFIGURE_TIMER_FOR_RUN_TIME_STATS()
        初始化用以提供运行时数据统计时钟的外围设备
    portGET_RUN_TIME_COUNTER_VALUE() 或 portALT_GET_RUN_TIME_COUNTER_VALUE()
        to return the current runtime statistics clock value
    
    UBaseType_t uxTaskGetSystemState( TaskStatus_t * const pxTaskStatusArray,
        const UBaseType_t uxArraySize,
        uint32_t * const pulTotalRunTime );

    typedef struct xTASK_STATUS
    {
        TaskHandle_t xHandle;
        const char *pcTaskName;
        UBaseType_t xTaskNumber;
        eTaskState eCurrentState;
        UBaseType_t uxCurrentPriority;
        UBaseType_t uxBasePriority;
        uint32_t ulRunTimeCounter;
        uint16_t usStackHighWaterMark;
    } TaskStatus_t;

    // 有效的前提是 configUSE_TRACE_FACILITY=1 且 configUSE_STATS_FORMATTING_FUNCTIONS=1
    void vTaskList( signed char *pcWriteBuffer )

     每一行提供一个任务的信息
       第一列 任务名称
       第二列 任务状态，R---READY，B---BLOCKED, S---Suspended, D---被删除 
       第三列 任务优先级
       第四列 the task’s stack high water mark
       第五列 任务ID


    // a very processor intensive function 
    // 函数执行期间，调度器处于挂起状态
    // 一般尽在调试状态下使用
    void vTaskGetRunTimeStats( signed char *pcWriteBuffer );
    每一行提供一个任务的信息
        第一列 任务名称
        第二列 任务运行耗时
        第三列 任务耗时占总耗时(自调度开始算起)的百分比

11.6 Trace Hook Macros
    在FreeRTOSConfig.h重新定义相关的宏

12. Trouble Shooting
    void vApplicationStackOverflowHook( TaskHandle_t *pxTask, signed char *pcTaskName )

